package com.qd.pay.service.order;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cdqidi.constant.ConstantDto;
import com.cdqidi.dto.ApiResult;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.exception.ApiException;
import com.cdqidi.util.RedisTemplateUtil;
import com.qd.pay.domain.OrderDTO;
import com.qd.pay.domain.OrderRespDTO;
import com.qd.pay.model.Order;
import com.qd.pay.model.OrderResp;
import com.qd.pay.repository.OrderRepository;
import com.qd.pay.service.OrderRespService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Service
@Slf4j
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final OrderRespService orderRespService;
    @Resource
    private RedisTemplateUtil<Order> redisTemplateUtil;

    public static final String PREFIX_ID = "m_order_orderId_{0}";
    public static final String PREFIX_OUT_TRADE_NO = "m_order_out_trade_no_{0}";
    public static final String PREFIX_THIRD_PARTY_ORDER = "m_order_third_party_order_{0}";
    public static final String SUCCESS = "SUCCESS";
    public static final String FAIL = "FAIL";
    public static final String CANCEL = "CANCEL";

    @Transactional(rollbackFor = Exception.class)
    public Order save(Order model) throws ApiException {
        try {
            if (!orderRepository.save(model)) {
                throw new ApiException(ApiResult.error());
            }
            return model;
        } finally {
            clearCache(model);
        }

    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean updateById(Order model) throws ApiException {
        final Order historyModel = Optional.ofNullable(getById(model.getOrderId())).orElseThrow(() -> new ApiException("ID不存在,不能更新"));
        try {
            if (!orderRepository.updateById(model)) {
                throw new ApiException(ApiResult.error());
            }
            return true;
        } finally {
            clearCache(historyModel);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean removeById(String orderId) throws ApiException {
        final Order historyModel = Optional.ofNullable(getById(orderId)).orElseThrow(() -> new ApiException("数据不存在,不能删除"));
        try {
            if (!orderRepository.removeById(orderId)) {
                throw new ApiException(ApiResult.error());
            }
            return true;
        } finally {
            clearCache(historyModel);
            historyModel.freeData();
        }
    }

    public Boolean isExists(String orderId) {
        return orderRepository.isExists(orderId);
    }

    public Order getById(String orderId) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, orderId);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY * 3), () -> orderRepository.getById(orderId), Order.class);
    }

    public Order getByOutTradeNo(String outTradeNo) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_OUT_TRADE_NO, outTradeNo);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY * 3), () -> orderRepository.getOne(new QueryWrapper<Order>().eq("out_trade_no", outTradeNo), false), Order.class);
    }


    public Order getByThirdPartyOrder(String thirdPartyOrder) {
        String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_THIRD_PARTY_ORDER, thirdPartyOrder);
        return redisTemplateUtil.get(new CacheKey().setKey(key).setTimeout(ConstantDto.REDIS_TIME_OUT_DAY * 3), () -> orderRepository.getOne(new QueryWrapper<Order>().eq("third_party_order", thirdPartyOrder), false), Order.class);
    }

    public IPage<OrderDTO> page(Page<OrderDTO> page, Order model) {
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>(model);
        IPage<Order> iPage = orderRepository.page(page.convert(this::dtoToModel), queryWrapper);
        return iPage.convert(this::modelToDto);
    }


    private void clearCache(Order model) {
        if (null == model) {
            return;
        }
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_OUT_TRADE_NO, model.getOutTradeNo()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_THIRD_PARTY_ORDER, model.getThirdPartyOrder()));
        redisTemplateUtil.deleteByKey(RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, model.getOrderId()));
    }

    public OrderDTO modelToDto(Order model) {
        if (null == model) {
            return null;
        }
        OrderDTO dto = new OrderDTO();
        dto.setOrderId(model.getOrderId());
        dto.setOutTradeNo(model.getOutTradeNo());
        dto.setThirdPartyOrder(model.getThirdPartyOrder());
        dto.setBody(model.getBody());
        dto.setPersonId(model.getPersonid());
        dto.setPayAmount(model.getPayAmount());
        dto.setIpAddress(model.getIpAddress());
        dto.setNotifyUrl(model.getNotifyUrl());
        dto.setState(model.getState());
        dto.setSendTime(model.getSendTime());
        dto.setApCode(model.getApCode());
        dto.setUpdateTime(model.getUpdateTime());
        dto.setType(model.getType());
        model.freeData();
        return dto;
    }

    public Order dtoToModel(OrderDTO dto) {
        if (null == dto) {
            return null;
        }
        Order model = new Order();
        model.setOrderId(dto.getOrderId());
        model.setOutTradeNo(dto.getOutTradeNo());
        model.setThirdPartyOrder(dto.getThirdPartyOrder());
        model.setBody(dto.getBody());
        model.setPersonid(dto.getPersonId());
        model.setPayAmount(dto.getPayAmount());
        model.setIpAddress(dto.getIpAddress());
        model.setNotifyUrl(dto.getNotifyUrl());
        model.setState(dto.getState());
        model.setSendTime(dto.getSendTime());
        model.setApCode(dto.getApCode());
        model.setSendTime(dto.getUpdateTime());
        model.setType(dto.getType());
        dto.freeData();
        return model;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateOrderState(OrderRespDTO dto) {
        String payCode = dto.getPayCode();
        payCode = payCode.toUpperCase();
        if (!payCode.equals(SUCCESS) && !payCode.equals(FAIL) && !payCode.equals(CANCEL)) {
            throw new ApiException("payCode值异常");
        }
        Order order = getById(dto.getOrderId());
        if (null != order) {
            LocalDateTime timeStart = dto.getTimeStart();
            if (null == timeStart) {
                timeStart = LocalDateTime.now();
            }
            OrderResp resp = orderRespService.getOne(dto.getOrderId());
            if (null == resp) {
                resp = new OrderResp();
                resp.setOrderId(dto.getOrderId());
                resp.setPayCode(payCode);
                resp.setTimeStart(timeStart);
                orderRespService.save(resp);
            } else {
                resp.setPayCode(payCode);
                resp.setTimeStart(timeStart);
                orderRespService.updateById(resp);
            }
            resp.freeData();
            order.freeData();
        }
    }

    /**
     * 按时间过滤没有获得回执消息的订单，只在测试上用
     * @param sendTime 时间，只取大于该时间的
     * @return 订单ID集合
     */
    @Transactional(readOnly = true)
    public List<Order> getNoReportOrder(String sendTime){
        return  Optional.ofNullable(orderRepository.getBaseMapper().getNoReportOrder(sendTime)).orElse(Collections.emptyList());
    }
}